/*
  * Copyright (c) 2022 Huawei Device Co., Ltd.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
    *
  * http://www.apache.org/licenses/LICENSE-2.0
    *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */

// THIS CODE IS GENERATED BY dataORM, DO NOT EDIT.
import { AbstractDao } from './AbstractDao';
import { Property, Constraint } from './Property';
import { DatabaseStatement } from './database/DatabaseStatement'
import { SQLiteStatement } from './database/SQLiteStatement'
import { Database } from './database/Database'
import { Query } from './query/Query';
import { SqlUtils } from './internal/SqlUtils'
import { StringBuilder } from './StringBuilder'
import { Entity } from './entity/Entity'
import { ToManyEntity } from './entity/ToManyEntity'
import { ToOneEntity } from './entity/ToOneEntity'
import { WhereCondition } from './query/WhereCondition';
import { GlobalContext } from './GlobalContext';
import HashMap from '@ohos.util.HashMap';
import { PropertyConverter } from './converter/PropertyConverter';
import { ConvertParameter } from './converter/ConvertParameter';
import ArrayList from '@ohos.util.ArrayList';
import { Queue } from './common/Queue';
import matrix4 from '@ohos.matrix4';
import { DbUtils } from './DbUtils';
import relationalStore from '@ohos.data.relationalStore';
import { getClsColumn, getClsColumns, getClsPropertiesArray, getClsTableInfo, IColumn } from './annotation/Util';
import { ValuesBucket } from '@kit.ArkData';
import { DaoLog } from './DaoLog';
import { StorageUtils } from './StorageUtils';
import { DaoTraceUtil, DaoTraceSpace } from './trace/DaoTraceUtil';
import { ToManyWithJoinEntity } from './entity/ToManyWithJoinEntity';

/**
 * DAO for table "NOTE".
 */

export class BaseDao<T, K> extends AbstractDao<T, K> {
  public propertyArr: Property[];
  private toManyListQuery: Query<T>;
  private entityUpdateAble: boolean | undefined | null;

  /** Creates the underlying database table. */
  public static async createTable<T>(delegate: Database, ifNotExists: boolean, t: T) {
    let autoMigration = await StorageUtils.getValueByKey(`${delegate.name}_autoMigration`, false)
    let upgrade = await StorageUtils.getValueByKey(`${delegate.name}_upgrade`, false)
    if (autoMigration && upgrade) {
      DaoLog.i(`${delegate.name} autoMigration:${autoMigration} needUpgrade:${upgrade}`);
    }
    let tbName = getClsTableInfo(t)
    let [createSql, arr] = BaseDao.getCreateTableSql(ifNotExists, t, !(autoMigration && upgrade));
    await delegate.execSQL(createSql);
    if (autoMigration && upgrade) {
      let cacheProperties: Property[] =
        await StorageUtils.getValueByKey(`${delegate.name}_${tbName.tableName}_properties`, [])
      try {
        await this.autoMigrationFunc(delegate, tbName.tableName, cacheProperties, arr);
        await StorageUtils.putValue(`${delegate.name}_${tbName.tableName}_properties`, arr);
      } catch (e) {
        DaoTraceUtil.startError("AbstractDao.createTable.error", DaoTraceSpace.TraceType.CRUD);
        DaoLog.e(`${e}`);
        DaoTraceUtil.finish("AbstractDao.createTable.error");
      }
    }
    let createIndexSqlArr: string[] = BaseDao.getCreateIndexSql(ifNotExists, t);
    if (createIndexSqlArr && createIndexSqlArr.length > 0) {
      for (let indexSql of createIndexSqlArr) {
        await delegate.execSQL(`${indexSql}`);
      }
    }
  }

  private static async autoMigrationFunc(delegate: Database, tbName: string, oldPros: Property[], newPros: Property[]) {
    let intersection = this.getIntersectionByColumnName(oldPros, newPros);
    if (intersection.length === 0) {
      return;
    }
    let columns =
      newPros.map(it => `${it.columnName} ${it.type} ${it.primaryKey ? 'PRIMARY KEY' : ''} ${(it.primaryKey &&
      it.autoincrement) ?
        'AUTOINCREMENT' : ''} ${it.constraint?.notNull ? 'NOT NULL' : ''} ${it.constraint?.uniques ? 'UNIQUE' : ''}`)
        .join(",");
    let unionPrimaryKey = newPros.filter(it => it.primaryKey && it.unionPrimaryKey).map(it => it.columnName).join(",")
    if (unionPrimaryKey.length > 0) {
      unionPrimaryKey = `, PRIMARY KEY (${unionPrimaryKey})`;
    }
    let createSql = `CREATE TABLE IF NOT EXISTS ${tbName}__temp__ (${columns}) ${unionPrimaryKey};`;
    try {
      delegate.beginTransaction();
      await delegate.execSQL(createSql)
      if (intersection.length > 0) {
        let cols = intersection.map(it => it.columnName).join(',')
        await delegate.execSQL(`INSERT INTO ${tbName}__temp__ (${cols}) SELECT ${cols} FROM ${tbName};`)
      }
      await delegate.execSQL(`DROP TABLE ${tbName};`)
      await delegate.execSQL(`ALTER TABLE ${tbName}__temp__ RENAME TO ${tbName};`)
      delegate.endTransaction();
    } catch (e) {
      delegate.rollBack();
      throw new Error(`AutoMigration fail:${e}`)
    }
  }

  private static getIntersectionByColumnName(arr1: Array<Property>, arr2: Array<Property>): Array<Property> {
    const set1 = new Set(arr1.map(item => `${item.name}-${item.columnName}-${item.type}`));
    const set2 = new Set(arr2.map(item => `${item.name}-${item.columnName}-${item.type}`));
    if (set1.size === set2.size && [...set1].every(value => set2.has(value))) {
      return [];
    }
    const namesSet = new Set(arr2.map(item => `${item.name}-${item.columnName}-${item.type}`));
    return arr1.filter(item => namesSet.has(`${item.name}-${item.columnName}-${item.type}`));
  }

  /**
   * Drops the underlying database table.
   * @deprecated since 2.1.1
   * @useinstead OpenHelper#dropTableAsync
   * */
  public static dropTable<T>(delegate: Database, ifExists: boolean, entityCls: T): void {
    const sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + BaseDao.TABLENAME(entityCls);
    delegate.execSQL(sql);
  }

  /**
   * Drops the underlying database table.
   * */
  public static async dropTableAsync<T>(delegate: Database, ifExists: boolean, entityCls: T): Promise<void> {
    const sql = "DROP TABLE " + (ifExists ? "IF EXISTS " : "") + BaseDao.TABLENAME(entityCls);
    await delegate.execSQL(sql);
  }

  protected bindValuesArray(stmt: SQLiteStatement | DatabaseStatement, entities: T[]): void {
    stmt.clearBindings();
    let properties: Property[] | undefined | null = null;
    entities.forEach(entity => {
      properties = properties ??
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(entity.constructor)];
      if (properties) {
        let record: ValuesBucket = {}
        for (let propertyName in properties) {
          let property = properties[propertyName];
          if (entity[property.name] !== null && entity[property.name] !== undefined) {
            record[property.columnName] = entity[property.name]
          }
          this.bindValuesEmbedded(property, record, entity)
          this.bindValuesConvert(property, record, entity)
        }
        stmt.pushValuesBucket(record);
      }
    })
  }

  private bindValuesEmbedded(property: Property, record: ValuesBucket, entity: ESObject) {
    // Embedded注解修饰处理
    if (property.sourcesPropertyArray && property.sourcesPropertyArray.size() > 0) {
      let sourcesNameArray: Queue<string> = property.sourcesPropertyArray.clone();
      if (sourcesNameArray) {
        let result: Object = entity;
        while (!(sourcesNameArray.empty())) {
          let lastProperty = sourcesNameArray.removeFirst();
          if (result[lastProperty]) {
            result = result[lastProperty];
          }
        }
        if (result[property.name]) {
          record[property.columnName] = result[property.name]
        }
      }
    }
  }

  private bindValuesConvert(property: Property, record: ValuesBucket, entity: ESObject) {
    let obj = property.getConvertParamObj() as ConvertParameter;
    if (obj) {
      let convertTarget = obj.getConverter();
      convertTarget = new convertTarget();
      if (convertTarget instanceof PropertyConverter) {
        record[property.columnName] = convertTarget.convertToDatabaseValue(entity[property.name]);
      }
    }
  }

  protected bindValues(stmt: DatabaseStatement | SQLiteStatement, entity: T): void {
    stmt.clearBindings();
    let properties =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(entity.constructor)];
    let value: ValuesBucket = {};
    if (properties) {
      for (let propertyName in properties) {
        let property = properties[propertyName];
        if (entity[property.name] !== null && entity[property.name] !== undefined) {
          value[property.columnName] = entity[property.name]
        }
        this.bindValuesEmbedded(property, value, entity)
        this.bindValuesConvert(property, value, entity)
      }
      stmt.setValuesBucket(value);
    }
  }

  public readKey(cursor: any, offset: number): any {
    return cursor.isColumnNull(offset + 0) ? undefined : cursor.getLong(offset + 0);
  }

  public readEntity(cursor: relationalStore.ResultSet, offset: number, isDeep = false): T {
    let entity = new this.entityCls();
    let row = cursor.getRow();
    let properties =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(this.entityCls)];
    // 存储Embedded修饰的Object
    let embeddedObjectMap = new HashMap<string, Object>();
    let objectMap = new HashMap<string, Object>();
    for (let key in properties) {
      let property = properties[key] as Property;
      let propertyValue = isDeep ? row[`${this.getTableName()}_${property.columnName}`] : row[property.columnName];
      // embedded注解修饰处理
      if (property.sourcesPropertyArray && property.sourcesPropertyArray.size() > 0) {
        let sourcesNameArray: Queue<string> = property.sourcesPropertyArray; //.clone();
        let array = sourcesNameArray.getElements();
        let result = entity;
        let parentsProperty = "";
        for (let i = 0; i < array.length; i++) {
          const element = array[i];
          parentsProperty += element;
          let sourcesPropertyName = property.sourcesPropertyArray.toArrayString();
          let sourcesTarget;
          if (embeddedObjectMap.hasKey(sourcesPropertyName)) {
            sourcesTarget = embeddedObjectMap.get(sourcesPropertyName);
          } else {
            sourcesTarget = {}; // 这里需要判断之前的entity里面是否已经存在过
            if (objectMap.hasKey(sourcesNameArray.toArrayString())) {
              sourcesTarget = objectMap.get(sourcesNameArray.toArrayString())
            }
            embeddedObjectMap.set(sourcesPropertyName, sourcesTarget);
          }
          sourcesTarget[property.name] = propertyValue;
          if (i === array.length - 1) {
            result[element] = sourcesTarget;
          } else {
            result[element] = result[element] ? result[element] : {};
            objectMap.set(parentsProperty, result[element]);
          }
          result = result[element];
        }
      } else {
        let queryValue = propertyValue;
        let convertParamObj = property.getConvertParamObj();
        if (convertParamObj) {
          let obj = convertParamObj as ConvertParameter;
          let convertTarget = obj.getConverter();
          convertTarget = new convertTarget();
          if (convertTarget instanceof PropertyConverter) {
            queryValue = convertTarget.convertToEntityProperty(queryValue);
          }
        }
        entity[property.name] = queryValue;
      }
    }
    embeddedObjectMap = null;
    objectMap = null;
    return entity;
  }

  /**
   * TODO readEntity 方法的无返回值
   */
  public readEntity2(cursor: any, entity: T, offset: number): void {
    let properties =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(entity.constructor)];
    let row = cursor.getRow();
    for (let key in properties) {
      let property = properties[key] as Property;
      entity[property.name] = row[property.columnName]
    }
  }

  protected updateKeyAfterInsert(entity: any, rowId: number): K {

    if (entity) {
      let properties =
        GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(this.entityCls)]
      if (properties) {
        for (let key in properties) {
          let property = properties[key];
          if (property && property.primaryKey) {
            entity[key] = rowId;
          }
        }
      }
    }

    let n;
    n = rowId;

    return n;
  }

  protected getKey(entity: any): K {

    if (entity) {
      let properties =
        GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(this.entityCls)]
      if (properties) {
        for (let key in properties) {
          let property = properties[key];
          if (property && property.primaryKey) {
            return entity[key];
          }
        }
      }
      return null;

    } else {
      return null;
    }
  }

  public hasKey(entity: any): boolean {
    if (entity) {
      let properties =
        GlobalContext.getContext().getValue(GlobalContext.KEY_CLS)[DbUtils.getEntityClassName(this.entityCls)]
      if (properties) {
        for (let key in properties) {
          let property = properties[key];
          if (property && property.primaryKey) {
            return entity[key] ? true : false;
          }
        }
      }
      return false;
    } else {
      return false;
    }
  }

  protected isEntityUpdateAble(): boolean {
    return this.entityUpdateAble ?? true;
  }

  public setEntityUpdateAble(entityUpdateAble: boolean){
    this.entityUpdateAble = entityUpdateAble;
  }

  private selectDeep: string;

  // toOne
  public getSelectDeep(byAlias = false): string {
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    let builder = new StringBuilder("SELECT ");
    if (byAlias) {
      builder.append(SqlUtils.appendColumnsWithAlias("", "T", entity.dbName, this.getAllColumns()));
    } else {
      builder.append(SqlUtils.appendColumns("", "T", this.getAllColumns()));
    }
    for (var i = 0; i < entity.toOneRelations.length; i++) {
      builder.append(',');
      let toOne: ToOneEntity = entity.toOneRelations[i];
      let targetClassName = toOne.targetEntityClsName;
      if (byAlias) {
        let targetEntity: Entity =
          GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[toOne.targetEntityClsName]
        builder.append(SqlUtils.appendColumnsWithAlias("", "T" + i, targetEntity.dbName, this.session.getDao(targetClassName).getAllColumns()));
      } else {
        builder.append(SqlUtils.appendColumns("", "T" + i, this.session.getDao(targetClassName).getAllColumns()));
      }
    }
    builder.append(" FROM " + entity.dbName + " T"); // 含有ToOne当前dao 的数据表名，
    for (var i = 0; i < entity.toOneRelations.length; i++) {
      let toOne = entity.toOneRelations[i]

      let targetEntity: Entity =
        GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[toOne.targetEntityClsName]
      builder.append(" LEFT JOIN " + targetEntity.dbName + " T" + i + " ON T." + toOne.fkProperties[0].columnName +
        "=T" + i + "." + targetEntity.pkProperty.columnName + "");

      builder.append(' ');
    }
    return builder.toString();
  }

  public async loadDeep(key: number): Promise<T> {
    this.assertSinglePk();
    if (key === null || key === undefined) {
      return null;
    }
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    if (entity.toOneRelations.length === 0) {
      DaoLog.i("Check @ToOne's annotations for creating tables")
      return undefined
    }
    let builder = new StringBuilder(this.getSelectDeep(true));
    builder.append("WHERE ");
    builder = new StringBuilder(SqlUtils.appendColumnsEqValue(builder.toString(), "T", this.getPkColumns()));
    let sql = builder.toString();
    let keyArray = new Array()
    keyArray.push(key.toString())
    let resultSet = await this.db.rawQueries(sql, keyArray);
    try {
      let available = resultSet.goToFirstRow();
      if (!available) {
        return null;
      }
      return this.loadCurrentDeep(resultSet, true, true);
    } finally {
      resultSet.close();
    }
  }

  public loadDeepSync(key: number): T {
    this.assertSinglePk();
    if (key === null || key === undefined) {
      return null;
    }
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    if (entity.toOneRelations.length === 0) {
      DaoLog.i("Check @ToOne's annotations for creating tables")
      return undefined
    }
    let builder = new StringBuilder(this.getSelectDeep(true));
    builder.append("WHERE ");
    builder = new StringBuilder(SqlUtils.appendColumnsEqValue(builder.toString(), "T", this.getPkColumns()));
    let sql = builder.toString();
    let keyArray = new Array()
    keyArray.push(key.toString())
    let resultSet = this.db.rawQueriesSync(sql, keyArray);
    try {
      let available = resultSet.goToFirstRow();
      if (!available) {
        return null;
      }
      return this.loadCurrentDeep(resultSet, true, true);
    } finally {
      resultSet.close();
    }
  }

  protected loadCurrentDeep(resultSet: any, lock: boolean, isDeep = false): T {
    let entity: T = this.loadCurrent(resultSet, 0, lock, isDeep);
    let offset: number = this.getAllColumns().length;
    let entityCls = this.getEntityCls();
    let entities: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    for (let i = 0; i < entities.toOneRelations.length; i++) {
      let toOne = entities.toOneRelations[i]
      let customer = this.loadCurrentOther(this.getSession().getDao(toOne.targetEntityClsName), resultSet, offset, isDeep);
      if (customer) {
        entity[toOne.name] = customer;
      }
    }
    return entity;
  }

  /**
   * 查询ToMany
   * @param toManyColumnName  被ToMany修饰的属性名
   * @param arr  源库关联的属性
   * @returns
   */
  public async queryToManyListByColumnName(toManyColumnName: string, arr: string[], isDeep = false): Promise<Array<T>> {
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    let toManyEntities = entity.incomingToManyRelations;
    if (toManyEntities.length > 0) {
      let toMany: ToManyEntity
      toManyEntities.forEach(element => {
        if (element.name === toManyColumnName) {
          toMany = element
        }
      });

      return await this._queryToManyList(toMany, arr, isDeep);
    } else {
      return null
    }
  }

  public queryToManyListByColumnNameSync(toManyColumnName: string, arr: string[], isDeep = false): Array<T>{
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    let toManyEntities = entity.incomingToManyRelations;
    if (toManyEntities.length > 0) {
      let toMany: ToManyEntity
      toManyEntities.forEach(element => {
        if (element.name === toManyColumnName) {
          toMany = element
        }
      });

      return this._queryToManyListSync(toMany, arr, isDeep);
    } else {
      return null
    }
  }


  /**
   *
   * let index =0;
   * let entityCls=this.getEntityCls();
   * let entity:Entity=globalThis.entityClsRelationship[entityCls.name]
   * let toMany= entity.incomingToManyRelations[index]
   * let arr:string[]
   * let columnValue="1233"
   * arr.push(columnKey)
   * BaseDao._queryToManyList(toMany,columnKey): Promise<Array<T>>
   *
   * */
  private async _queryToManyList(toMany: ToManyEntity | ToManyWithJoinEntity, arr: string[], isDeep = false): Promise<Array<T>> {
    if (isDeep && !(toMany instanceof ToManyWithJoinEntity)) {
      const select = this.getSelectDeep(true)
      let whereConditions: string[] = [];
      for (let index = 0; index < toMany.targetProperties.length; index++) {
        let property = <Property> toMany.targetProperties[index]
        whereConditions.push(`T.${property.columnName} =?`)
      }
      let sqlBuilder: StringBuilder;
      if (whereConditions.length > 0) {
        sqlBuilder = new StringBuilder(`${select} WHERE ${whereConditions.join(' AND ')}`)
      } else {
        sqlBuilder = new StringBuilder(`${select}`)
      }
      if (toMany.orderBy) {
        sqlBuilder.append(` ORDER BY ${toMany?.orderBy?.split(",").map(it => `T.${it}`).join(",")}`)
      }
      let cursor = await this.db.rawQueries(sqlBuilder.toString(), arr);
      return this.loadDeepAllAndCloseCursor(cursor, true);
    }

    let queryBuilder = this.queryBuilder(); // target
    // @ts-ignore
    if (toMany.joinEntity === undefined || toMany.joinEntity === null) {
      for (let index = 0; index < toMany.targetProperties.length; index++) {
        let property = <Property> toMany.targetProperties[index]
        let cond = <WhereCondition> property.eqSql(null)
        queryBuilder.whereSql(cond);
      }
    } else {
      let joinEntityName = toMany.targetEntityClsName
      queryBuilder.join(joinEntityName, toMany.targetProperties[0])
        .whereSql(toMany.sourceProperties[0].eqSql(arr[0]));
    }
    queryBuilder.orderRaw(toMany?.orderBy?.split(",").map(it => `T.${it}`).join(",") ?? "")
    this.toManyListQuery = queryBuilder.buildSql();

    let query = this.toManyListQuery.forCurrentThread();
    for (let i = 0; i < arr.length; i++) {
      query.setParameter(i, arr[i]);
    }

    return await query.listSql();
  }

  private _queryToManyListSync(toMany: ToManyEntity | ToManyWithJoinEntity, arr: string[], isDeep = false): Array<T> {
    if (isDeep && !(toMany instanceof ToManyWithJoinEntity)) {
      const select = this.getSelectDeep(true)
      let whereConditions: string[] = [];
      for (let index = 0; index < toMany.targetProperties.length; index++) {
        let property = <Property> toMany.targetProperties[index]
        whereConditions.push(`T.${property.columnName} =?`)
      }
      let sqlBuilder: StringBuilder;
      if (whereConditions.length > 0) {
        sqlBuilder = new StringBuilder(`${select} WHERE ${whereConditions.join(' AND ')}`)
      } else {
        sqlBuilder = new StringBuilder(`${select}`)
      }
      if (toMany.orderBy) {
        sqlBuilder.append(` ORDER BY ${toMany?.orderBy?.split(",").map(it => `T.${it}`).join(",")}`)
      }
      let cursor = this.db.rawQueriesSync(sqlBuilder.toString(), arr);
      return this.loadDeepAllAndCloseCursor(cursor, true);
    }

    let queryBuilder = this.queryBuilder(); // target
    // @ts-ignore
    if (toMany.joinEntity === undefined || toMany.joinEntity === null) {
      for (let index = 0; index < toMany.targetProperties.length; index++) {
        let property = <Property> toMany.targetProperties[index]
        let cond = <WhereCondition> property.eqSql(null)
        queryBuilder.whereSql(cond);
      }
    } else {
      let joinEntityName = toMany.targetEntityClsName
      queryBuilder.join(joinEntityName, toMany.targetProperties[0])
        .whereSql(toMany.sourceProperties[0].eqSql(arr[0]));
    }
    queryBuilder.orderRaw(toMany?.orderBy?.split(",").map(it => `T.${it}`).join(",") ?? "")
    this.toManyListQuery = queryBuilder.buildSql();

    let query = this.toManyListQuery.forCurrentThread();
    for (let i = 0; i < arr.length; i++) {
      query.setParameter(i, arr[i]);
    }

    return query.listSqlSync();
  }

  /** Reads all available rows from the given cursor and returns a list of new ImageTO objects. */
  public loadAllDeepFromCursor(resultSet: any, isDeep = false): Array<T> {
    let count = resultSet.rowCount;
    let list = new Array<T>();
    if (resultSet.goToFirstRow()) {
      do {
        list.push(this.loadCurrentDeep(resultSet, false, isDeep));
      } while (resultSet.goToNextRow());
    }
    return list;
  }

  /**
   * @param isDeep 表示是否合并表名
   */
  protected loadDeepAllAndCloseCursor(resultSet: any, isDeep = false): Array<T> {
    try {
      return this.loadAllDeepFromCursor(resultSet, isDeep);
    } finally {
      resultSet.close();
    }
  }

  /** A raw-style query where you can pass any WHERE clause and arguments. */
  public async queryDeep(where: string, selectionArg: string[]): Promise<Array<T>> {
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    if (entity.toOneRelations.length === 0) {
      DaoTraceUtil.startInfo("AbstractDao.queryDeep", DaoTraceSpace.TraceType.CRUD);
      DaoLog.i("Check @ToOne's annotations for creating tables")
      DaoTraceUtil.finish("AbstractDao.queryDeep");
      return undefined
    }
    let cursor = await this.db.rawQueries(this.getSelectDeep(true) + where, selectionArg);
    return this.loadDeepAllAndCloseCursor(cursor, true);
  }

  public queryDeepSync(where: string, selectionArg: string[]): Array<T> {
    let entityCls = this.getEntityCls();
    let entity: Entity =
      GlobalContext.getContext().getValue(GlobalContext.KEY_CLS_RE_SHIP)[DbUtils.getEntityClassName(entityCls)]
    if (entity.toOneRelations.length === 0) {
      DaoLog.i("Check @ToOne's annotations for creating tables")
      return undefined
    }
    let cursor = this.db.rawQueriesSync(this.getSelectDeep(true) + where, selectionArg);
    return this.loadDeepAllAndCloseCursor(cursor, true);
  }
}
